package org.iryshka.skyserver;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.Arrays;

import org.iryshka.logit.ConsoleFileLogger;

public class SkyThread extends Thread {
	// buffer size for client request should be 2MB by default
	private static int CLIENT_BUFFER_MAX_SIZE = 1 << 21;

	private final Socket clientSock;

	public SkyThread(final Socket clientSock) {
		this.clientSock = clientSock;
	}

	@Override
	public void run() {
		try {
			ConsoleFileLogger.logIt(clientSock.getRemoteSocketAddress().toString() + " connected");

			HTTPRequest request = getHttpRequestFromSocketStream(clientSock.getInputStream());

			ConsoleFileLogger.logIt("Request from " + clientSock.getRemoteSocketAddress() + " has been analyzed");

			HTTPRequestHandler handler = new HTTPRequestHandler();
			HTTPResponse response = handler.processHttpRequest(request);
			writeResponse(response);

			ConsoleFileLogger.logIt("Response to "+ clientSock.getRemoteSocketAddress() + " has been sent");

		} catch (Exception e) {
			ConsoleFileLogger.logIt("Exception occured in " + this.getName() + " " + e);
		} finally {
			String remoteAddr = clientSock.getRemoteSocketAddress().toString();
			if (clientSock != null) {
				try {
					clientSock.close();
					ConsoleFileLogger.logIt(remoteAddr + " disconnected");
				} catch (final IOException e) {
					ConsoleFileLogger.logIt("Failed to close socket for "+ remoteAddr + ". Exception: " + e);
				}
			}

			SkyWebServer.notifyThreadCompleted(this);
		}
	}
	
	// finding out requestMethod and resourceName
	public void parseHeader(HTTPRequest request, String header) {
		String resourceStr = null;

		if (header.startsWith("GET ")) {
			request.setRequestMethod("GET");

			resourceStr = header.substring(4, header.indexOf("HTTP"));
			resourceStr = resourceStr.trim();

			while (resourceStr.endsWith("/") && resourceStr.length() > 1) {
				resourceStr = resourceStr.substring(0, resourceStr.length() - 1);
			}

			request.setResourceName(resourceStr);
		} else if (header.startsWith("POST ")) {
			request.setRequestMethod("POST");
		} else if (header.startsWith("HEAD ")) {
			request.setRequestMethod("HEAD");
		} else if (header.startsWith("DELETE ")) {
			request.setRequestMethod("DELETE");
		} else {
			final String[] strArr = header.split(":");
			if (strArr != null)	request.setHeader(strArr[0], strArr[1]);
		}
	}

	private HTTPRequest getHttpRequestFromSocketStream(InputStream inputStream) throws IOException {
		final HTTPRequest httpRequest = new HTTPRequest();

		final ByteArrayOutputStream buffer = new ByteArrayOutputStream();

		final byte[] buff = new byte[CLIENT_BUFFER_MAX_SIZE + 1];
		int bytesRead = inputStream.read(buff);
		if (bytesRead == buff.length) {
			throw new RuntimeException("Too much data in HTTP request (>" + CLIENT_BUFFER_MAX_SIZE/(1024 * 1024) + "MB)");
		} else if (bytesRead == 0) {
			throw new RuntimeException("Empty HTTP request");
		}
		
		buffer.write(buff, 0, bytesRead);

		String requestHeaders = null;
		final byte[] byteArray = buffer.toByteArray();
		int i = 0;
		for (i = 0; i < byteArray.length - 3; i++) {
			if (byteArray[i] == '\r' && byteArray[i + 1] == '\n'
					&& byteArray[i + 2] == '\r' && byteArray[i + 3] == '\n') {
				requestHeaders = new String(byteArray, 0, i);
				break;
			}
		}

		if (requestHeaders == null) {
			throw new RuntimeException("No request headers section in request");
		}

		final byte[] requestData = Arrays.copyOfRange(byteArray, i + 4, byteArray.length);
		httpRequest.setRequestData(requestData);

		final String[] headersAsArray = requestHeaders.split("\r\n");
		ConsoleFileLogger.logIt(requestHeaders);
		for (final String header : headersAsArray) {
			parseHeader(httpRequest, header);
		}
		
		if (httpRequest.getRequestMethod() == null || !(httpRequest.getRequestMethod().equals("GET") ||  
				httpRequest.getRequestMethod().equals("POST") || httpRequest.getRequestMethod().equals("DELETE")
				|| httpRequest.getRequestMethod().equals("HEAD"))) {
			throw new RuntimeException("HTTP request doesn't contain valid HTTP method");
		}
		
		return httpRequest;
	}

	private void writeResponse(final HTTPResponse httpResponse)
			throws IOException, SocketException {
		final OutputStream out = clientSock.getOutputStream();

		out.write(httpResponse.toString().getBytes());

		if (httpResponse.getResourceStream() != null) {
			final byte[] buff = new byte[1024];
			final InputStream in = httpResponse.getResourceStream();

			int partToWrite = 0;
			try {
				while ((partToWrite = in.read(buff, 0, 1024)) != -1) {
					out.write(buff, 0, partToWrite);
				}
				out.flush();
			} finally {
				try {
					in.close();
				} catch (final IOException e) {
				}
			}
		}
	}
}